package in.co.codedoc.sql;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;

public abstract class SQLStatement
{
	public void Invalidate() 
	{
		if(ps != null)
		{
			try
			{
				ps.close();
			}
			catch (SQLException e)
			{
				throw new RuntimeException(e);
			}
			
			ps = null;
		}
	}
	
	protected void Prepare()
		throws SQLException, IOException
	{
		if(!IsPrepared())
		{
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			
			Generate(pw);
			
			pw.flush();
			pw.close();
			
			if(Logger.logger.isDebugEnabled())
			{
				Logger.logger.debug("Preparing SQL " + sw.toString());
			}
			
			Prepare(sw.toString());
		}
	}
	
	private void Prepare(String sql) throws SQLException
	{
		Invalidate();
		
		ps = DBInteractions.GetConnection().prepareStatement(sql);
	}
	
	protected void Bind(LinkedList<DBInputColumnMap> columnMaps) 
		throws SQLException
	{
		if(Logger.logger.isDebugEnabled())
		{
			PushDebugStringBuffer();
			GetDebugStringBuffer().append("Binding [");
		}
		
		int index = 1;
		for(DBInputColumnMap columnMap:columnMaps)
		{
			columnMap.SetParameter(index, ps);
			index++;
		}

		if(Logger.logger.isDebugEnabled())
		{
			GetDebugStringBuffer().append("]");
			
			Logger.logger.debug(PopDebugStringBuffer().toString());
		}
	}
	
	public static void PushDebugStringBuffer()
	{
		debugStringBufferHolder.get().push(new StringBuffer());
	}
	
	public static StringBuffer PopDebugStringBuffer()
	{
		return debugStringBufferHolder.get().pop();
	}
	
	public static StringBuffer GetDebugStringBuffer()
	{
		return debugStringBufferHolder.get().peek();
	}

	private static ThreadLocal<Stack<StringBuffer>> debugStringBufferHolder
		= new ThreadLocal<Stack<StringBuffer>>()
			{
				@Override
				protected Stack<StringBuffer> initialValue()
				{
					return new Stack<StringBuffer>();
				}
			};
	
	protected boolean IsPrepared()
	{
		return ps != null;
	}

	public void Execute() 
	{
		try
		{
			Prepare();
			
			LinkedList<DBInputColumnMap> columnMaps 
				= new LinkedList<DBInputColumnMap>();
			
			GetParameterValues(columnMaps);
			
			Bind(columnMaps);

			ps.execute();
		}
		catch (Throwable th)
		{
			throw new RuntimeException(th);
		}
	}

	public static Set<Table> GatherTables(List<TableColumn> columns)
	{
		Set<Table> tables = new HashSet<Table>();

		for(TableColumn tc:columns)
		{
			tables.add(tc.GetTable());
		}
		
		return tables;
	}
	
	public boolean ExecuteUpdate()
	{
		try
		{
			Execute();
			
			return GetUpdateCount() != 0;
		}
		catch (Throwable th)
		{
			throw new RuntimeException(th);
		}
	}
	
	protected int GetUpdateCount() 
		throws SQLException
	{
		return ps.getUpdateCount();
	}

	protected ResultSet GetResultSet() 
		throws SQLException
	{
		return ps.getResultSet();
	}
	
	public abstract void GetParameterValues(LinkedList<DBInputColumnMap> values);
	
	public abstract PrintWriter Generate(PrintWriter out)
		throws IOException;
	
	private PreparedStatement ps;
}
